﻿// 2022/4/1: 首个版本
// 2022/10/12: SetConfig过滤相同ID设备
// 2022/11/3: 新增VideoDeviceMode，VideoDeviceConfig.OutputMode
// 2022/11/21: GetConfig增加enabled字段兼容旧版本
// 2022/12/30: SetConfig兼容enabled字段
// 2023/2/20: VideoChannelConfig.RemoteSize/Codec/RecordFPS/AlignedFPS
// 2023/4/10: 增加Load/Save方法
// 2023/7/6: 处理线程数不能超过本机CPU核数的一半，且默认值改为4
// 2023/10/9: 添加英文注释
// 2023/10/20: 修正VFlip方向
// 2024/1/29: GetConfigStatus输出errorHint
// 2024/3/26: Remote相关字段标注为deprecated
// 2024/5/28: 新增CustomizedParameters
// 2024/7/17: 支持读取简化的数值配置
// 2024/7/17: 修正PinholeIntrinsicsFromXml
// 2025/2/10: 新增SetChannelDevices

using System;
using System.Collections.Generic;
using System.Xml;
using ASEva;
using ASEva.Utility;
using ASEva.Samples;
using SO = System.Reflection.ObfuscationAttribute;

namespace CommonSource
{
    [SO]
    enum VideoDeviceMode
    {
        Disabled = 0,
        Input = 1,
        Output = 2,
    }

    class VideoDeviceConfig
    {
        public VideoDeviceMode Mode { get; set; }
        public VideoDeviceID ID { get; set; }
        public int ChannelIndex { get; set; }
        public VideoInputMode InputMode { get; set; }
        public VideoOutputMode OutputMode { get; set; }
        public int FrameRate { get; set; }
        public bool AlignedFPS { get; set; }

        public VideoDeviceConfig()
        {
            Mode = VideoDeviceMode.Disabled;
            InputMode = new VideoInputMode() { InputCodec = VideoDataCodec.H264, Size = new IntSize(640, 480) };
            OutputMode = new VideoOutputMode() { OutputCodec = VideoDataCodec.H264, Size = new IntSize(640, 480) };
            FrameRate = 15;
            AlignedFPS = false;
            ChannelIndex = 0;
        }
    }

    class VideoChannelDevice
    {
        public String Name { get; set; }
        public bool IsOutput { get; set; }
        public VideoDeviceID ID { get; set; }
        public VideoDataCodec Codec { get; set; }
        public IntSize Size { get; set; }

        // The following properties are only available while IsOutput=false
        public int FrameRate { get; set; }
        public bool Invert { get; set; }
        public bool VFlip { get; set; }
        public double HFOV { get; set; }
    }

    enum VideoParamFixLevel
    {
        Manual = 1, // Manually editable / 可手动更改
        File = 2, // Can be modified by file / 可导入文件更改
        Fixed = 3, // Can't be modified 不可更改
    }

    struct VideoParameter
    {
        public double defaultValue;
        public VideoParamFixLevel fix; // You cannot change it manually after fixed, you must reset the channel before you can change it / fix之后将不能手动更改，必须reset该通道后才能更改
        public double value;

        public VideoParameter(double defaultValue)
        {
            this.defaultValue = defaultValue;
            this.fix = VideoParamFixLevel.Manual;
            this.value = defaultValue;
        }

        public VideoParameter(double defaultValue, double value, VideoParamFixLevel fixLevel)
        {
            this.defaultValue = defaultValue;
            this.fix = fixLevel;
            this.value = value;
        }

        public VideoParameter(double defaultValue, String text)
        {
            this.defaultValue = defaultValue;
            this.fix = VideoParamFixLevel.Manual;
            this.value = defaultValue;
            if (!String.IsNullOrEmpty(text))
            {
                var comps = text.Split(':');
                if (comps.Length == 2)
                {
                    fix = (VideoParamFixLevel)Convert.ToInt32(comps[0]);
                    value = Convert.ToDouble(comps[1]);
                }
                else if (comps.Length == 1)
                {
                    fix = VideoParamFixLevel.Manual;
                    value = Convert.ToDouble(comps[0]);
                }
            }
        }

        public VideoParameter(double defaultValue, VideoParamFixLevel defaultLevel, String text)
        {
            this.defaultValue = defaultValue;
            this.fix = defaultLevel;
            this.value = defaultValue;
            if (!String.IsNullOrEmpty(text))
            {
                var comps = text.Split(':');
                if (comps.Length == 2)
                {
                    fix = (VideoParamFixLevel)Convert.ToInt32(comps[0]);
                    value = Convert.ToDouble(comps[1]);
                }
            }
        }

        public bool IsManualEnabled()
        {
            return fix == VideoParamFixLevel.Manual;
        }

        public VideoParameter ManualSet(double newValue)
        {
            if (fix != VideoParamFixLevel.Manual) return this;
            else return new VideoParameter(defaultValue, newValue, VideoParamFixLevel.Manual);
        }

        public VideoParameter ToFileLevel()
        {
            if (fix != VideoParamFixLevel.Manual) return this;
            else return new VideoParameter(defaultValue, value, VideoParamFixLevel.File);
        }

        public VideoParameter FileSet(double newValue)
        {
            if (fix != VideoParamFixLevel.File) return this;
            else return new VideoParameter(defaultValue, newValue, VideoParamFixLevel.File);
        }

        public VideoParameter ToFixedLevel()
        {
            if (fix == VideoParamFixLevel.Fixed) return this;
            else return new VideoParameter(defaultValue, value, VideoParamFixLevel.Fixed);
        }

        public VideoParameter ToFixedLevel(double fixedValue)
        {
            if (fix == VideoParamFixLevel.Fixed) return this;
            else return new VideoParameter(defaultValue, fixedValue, VideoParamFixLevel.Fixed);
        }

        public VideoParameter Reset()
        {
            return new VideoParameter(defaultValue);
        }

        public override string ToString()
        {
            return (int)fix + ":" + value;
        }

        public static bool IsFileLevelAvailable(VideoParameter[] parameters)
        {
            foreach (var param in parameters)
            {
                if (param.fix == VideoParamFixLevel.Fixed) return false;
            }
            return true;
        }
    }

    interface VideoParameters
    {
        SpecialCameraType GetSpecialType();
        void ToXml(XmlElement node);
        void FromXml(XmlElement node);
        void Reset();
    }

    class PinholeIntrinsicsParameters
    {
        public VideoParameter Invert { get; set; }
        public VideoParameter VFlip { get; set; }
        public VideoParameter Undistort { get; set; }
        public VideoParameter HFOV { get; set; }
        public VideoParameter AspectRatio { get; set; }
        public VideoParameter CU { get; set; }
        public VideoParameter CV { get; set; }
        public VideoParameter K1 { get; set; }
        public VideoParameter K2 { get; set; }
        public VideoParameter K3 { get; set; }
        public VideoParameter K4 { get; set; }
        public VideoParameter K5 { get; set; }
        public VideoParameter K6 { get; set; }
        public VideoParameter P1 { get; set; }
        public VideoParameter P2 { get; set; }

        public PinholeIntrinsicsParameters()
        {
            Invert = new VideoParameter(0);
            VFlip = new VideoParameter(0);
            Undistort = new VideoParameter(0);
            HFOV = new VideoParameter(45);
            AspectRatio = new VideoParameter(1);
            CU = new VideoParameter(0);
            CV = new VideoParameter(0);
            K1 = new VideoParameter(0);
            K2 = new VideoParameter(0);
            K3 = new VideoParameter(0);
            K4 = new VideoParameter(0);
            K5 = new VideoParameter(0);
            K6 = new VideoParameter(0);
            P1 = new VideoParameter(0);
            P2 = new VideoParameter(0);

            AspectRatio = AspectRatio.ToFileLevel();
            CU = CU.ToFileLevel();
            CV = CV.ToFileLevel();
            K1 = K1.ToFileLevel();
            K2 = K2.ToFileLevel();
            K3 = K3.ToFileLevel();
            K4 = K4.ToFileLevel();
            K5 = K5.ToFileLevel();
            K6 = K6.ToFileLevel();
            P1 = P1.ToFileLevel();
            P2 = P2.ToFileLevel();
        }

        public void PinholeIntrinsicsToXml(XmlElement node)
        {
            var cw = new AttributeWriter(node);
            cw.WriteString("inversion", Invert.ToString());
            cw.WriteString("vertical_flip", VFlip.ToString());
            var legacyInvert = new VideoParameter(Invert.defaultValue, (int)Invert.value ^ (int)VFlip.value, Invert.fix);
            cw.WriteString("invert", legacyInvert.ToString());
            cw.WriteString("vflip", VFlip.ToString());
            cw.WriteString("undistort", Undistort.ToString());
            cw.WriteString("hfov", HFOV.ToString());
            cw.WriteString("aspectratio", AspectRatio.ToString());
            cw.WriteString("cu", CU.ToString());
            cw.WriteString("cv", CV.ToString());
            cw.WriteString("k1", K1.ToString());
            cw.WriteString("k2", K2.ToString());
            cw.WriteString("k3", K3.ToString());
            cw.WriteString("k4", K4.ToString());
            cw.WriteString("k5", K5.ToString());
            cw.WriteString("k6", K6.ToString());
            cw.WriteString("p1", P1.ToString());
            cw.WriteString("p2", P2.ToString());
        }

        public void PinholeIntrinsicsFromXml(XmlElement node)
        {
            var cp = new AttributeParser(node);
            if (node.Attributes["inversion"] != null)
            {
                Invert = new VideoParameter(0, cp.ParseString("inversion", null));
                VFlip = new VideoParameter(0, cp.ParseString("vertical_flip", null));
            }
            else
            {
                var rawInvert = new VideoParameter(0, cp.ParseString("invert", null));
                VFlip = new VideoParameter(0, cp.ParseString("vflip", null));
                Invert = new VideoParameter(rawInvert.defaultValue, (int)rawInvert.value ^ (int)VFlip.value, rawInvert.fix);
            }
            Undistort = new VideoParameter(0, cp.ParseString("undistort", null));
            HFOV = new VideoParameter(45, cp.ParseString("hfov", null));
            AspectRatio = new VideoParameter(1, cp.ParseString("aspectratio", null));
            CU = new VideoParameter(0, cp.ParseString("cu", null));
            CV = new VideoParameter(0, cp.ParseString("cv", null));
            K1 = new VideoParameter(0, cp.ParseString("k1", null));
            K2 = new VideoParameter(0, cp.ParseString("k2", null));
            K3 = new VideoParameter(0, cp.ParseString("k3", null));
            K4 = new VideoParameter(0, cp.ParseString("k4", null));
            K5 = new VideoParameter(0, cp.ParseString("k5", null));
            K6 = new VideoParameter(0, cp.ParseString("k6", null));
            P1 = new VideoParameter(0, cp.ParseString("p1", null));
            P2 = new VideoParameter(0, cp.ParseString("p2", null));
        }

        public void ModifyAllIntrinsicsToFixedLevel()
        {
            Invert = Invert.ToFixedLevel();
            VFlip = VFlip.ToFixedLevel();
            Undistort = Undistort.ToFixedLevel();
            HFOV = HFOV.ToFixedLevel();
            AspectRatio = AspectRatio.ToFixedLevel();
            CU = CU.ToFixedLevel();
            CV = CV.ToFixedLevel();
            K1 = K1.ToFixedLevel();
            K2 = K2.ToFixedLevel();
            K3 = K3.ToFixedLevel();
            K4 = K4.ToFixedLevel();
            K5 = K5.ToFixedLevel();
            K6 = K6.ToFixedLevel();
            P1 = P1.ToFixedLevel();
            P2 = P2.ToFixedLevel();
        }

        public void ResetAllIntrinsics()
        {
            Invert = Invert.Reset();
            VFlip = VFlip.Reset();
            Undistort = Undistort.Reset();
            HFOV = HFOV.Reset();
            AspectRatio = AspectRatio.Reset().ToFileLevel();
            CU = CU.Reset().ToFileLevel();
            CV = CV.Reset().ToFileLevel();
            K1 = K1.Reset().ToFileLevel();
            K2 = K2.Reset().ToFileLevel();
            K3 = K3.Reset().ToFileLevel();
            K4 = K4.Reset().ToFileLevel();
            K5 = K5.Reset().ToFileLevel();
            K6 = K6.Reset().ToFileLevel();
            P1 = P1.Reset().ToFileLevel();
            P2 = P2.Reset().ToFileLevel();
        }

        public bool IsUndistortNecessary()
        {
            return AspectRatio.value != 1 || K1.value != 0 || K2.value != 0 || K3.value != 0 || K4.value != 0 || K5.value != 0 || K6.value != 0 || P1.value != 0 || P2.value != 0;
        }
    }

    class NormalVideoParameters : PinholeIntrinsicsParameters, VideoParameters
    {
        public VideoParameter RectifyRoll { get; set; }
        public VideoParameter RectifyPitch { get; set; }
        public VideoParameter RectifyYaw { get; set; }
        public VideoParameter OX { get; set; }
        public VideoParameter OY { get; set; }
        public VideoParameter OZ { get; set; }
        public VideoParameter Yaw { get; set; }
        public VideoParameter Pitch { get; set; }
        public VideoParameter Roll { get; set; }
        public VideoParameter SpecialHint { get; set; } // 0: normal, 1: any, 2: front

        public double RectifiedYaw
        {
            get
            {
                if (Yaw.value >= -90 && Yaw.value <= 90) return 0;
                else if (Yaw.value > 90 || Yaw.value < -90) return 180;
                else return 0;
            }
        }

        public NormalVideoParameters()
        {
            RectifyRoll = new VideoParameter(0);
            RectifyPitch = new VideoParameter(0);
            RectifyYaw = new VideoParameter(0);
            OX = new VideoParameter(0);
            OY = new VideoParameter(0);
            OZ = new VideoParameter(1);
            Yaw = new VideoParameter(0);
            Pitch = new VideoParameter(0);
            Roll = new VideoParameter(0);
            SpecialHint = new VideoParameter(0);
        }

        public SpecialCameraType GetSpecialType()
        {
            switch ((int)SpecialHint.value)
            {
                case 0:
                    return SpecialCameraType.Normal;
                case 1:
                    return SpecialCameraType.AnyCamera;
                case 2:
                    return SpecialCameraType.FrontCamera;
                default:
                    return SpecialCameraType.Normal;
            }
        }

        public void ToXml(XmlElement node)
        {
            PinholeIntrinsicsToXml(node);

            var cw = new AttributeWriter(node);
            cw.WriteString("rectify_roll", RectifyRoll.ToString());
            cw.WriteString("rectify_pitch", RectifyPitch.ToString());
            cw.WriteString("rectify_yaw", RectifyYaw.ToString());
            cw.WriteDouble("rectified_yaw", RectifiedYaw);
            cw.WriteString("ox", OX.ToString());
            cw.WriteString("oy", OY.ToString());
            cw.WriteString("oz", OZ.ToString());
            cw.WriteString("yaw", Yaw.ToString());
            cw.WriteString("pitch", Pitch.ToString());
            cw.WriteString("roll", Roll.ToString());
            cw.WriteString("special_hint", SpecialHint.ToString());
        }

        public void FromXml(XmlElement node)
        {
            PinholeIntrinsicsFromXml(node);

            var cp = new AttributeParser(node);
            RectifyRoll = new VideoParameter(0, cp.ParseString("rectify_roll", null));
            RectifyPitch = new VideoParameter(0, cp.ParseString("rectify_pitch", null));
            RectifyYaw = new VideoParameter(0, cp.ParseString("rectify_yaw", null));
            OX = new VideoParameter(0, cp.ParseString("ox", null));
            OY = new VideoParameter(0, cp.ParseString("oy", null));
            OZ = new VideoParameter(1, cp.ParseString("oz", null));
            Yaw = new VideoParameter(0, cp.ParseString("yaw", null));
            Pitch = new VideoParameter(0, cp.ParseString("pitch", null));
            Roll = new VideoParameter(0, cp.ParseString("roll", null));
            SpecialHint = new VideoParameter(0, cp.ParseString("special_hint", null));
        }


        public void Reset()
        {
            ResetAllIntrinsics();

            RectifyRoll = RectifyRoll.Reset();
            RectifyPitch = RectifyPitch.Reset();
            RectifyYaw = RectifyYaw.Reset();
            OX = OX.Reset();
            OY = OY.Reset();
            OZ = OZ.Reset();
            Yaw = Yaw.Reset();
            Pitch = Pitch.Reset();
            Roll = Roll.Reset();
            SpecialHint = SpecialHint.Reset();
        }
    }

    class LaneLineVideoParameters : PinholeIntrinsicsParameters, VideoParameters
    {
        public VideoParameter ZeroPointU { get; set; }
        public VideoParameter ZeroPointV { get; set; }
        public VideoParameter MeterPointU { get; set; }
        public VideoParameter MeterPointV { get; set; }
        public VideoParameter FiftyRatio { get; set; }
        public VideoParameter MeterScale { get; set; }
        public VideoParameter SpecialHint { get; set; } // 0: left, 1: right

        public LaneLineVideoParameters()
        {
            ZeroPointU = new VideoParameter(0.25);
            ZeroPointV = new VideoParameter(0);
            MeterPointU = new VideoParameter(-0.25);
            MeterPointV = new VideoParameter(0);
            FiftyRatio = new VideoParameter(0.5);
            MeterScale = new VideoParameter(1);
            SpecialHint = new VideoParameter(0);

            ZeroPointU = ZeroPointU.ToFileLevel();
            ZeroPointV = ZeroPointV.ToFileLevel();
            MeterPointU = MeterPointU.ToFileLevel();
            MeterPointV = MeterPointV.ToFileLevel();
            FiftyRatio = FiftyRatio.ToFileLevel();
            MeterScale = MeterScale.ToFileLevel();
        }

        public SpecialCameraType GetSpecialType()
        {
            return SpecialHint.value == 1 ? SpecialCameraType.RightLaneCamera : SpecialCameraType.LeftLaneCamera;
        }

        public void ToXml(XmlElement node)
        {
            PinholeIntrinsicsToXml(node);

            var cw = new AttributeWriter(node);
            cw.WriteString("zero_u", ZeroPointU.ToString());
            cw.WriteString("zero_v", ZeroPointV.ToString());
            cw.WriteString("meter_u", MeterPointU.ToString());
            cw.WriteString("meter_v", MeterPointV.ToString());
            cw.WriteString("fifty", FiftyRatio.ToString());
            cw.WriteString("meter_scale", MeterScale.ToString());
            cw.WriteString("special_hint", SpecialHint.ToString());
        }

        public void FromXml(XmlElement node)
        {
            PinholeIntrinsicsFromXml(node);

            var cp = new AttributeParser(node);
            ZeroPointU = new VideoParameter(0.25, cp.ParseString("zero_u", null));
            ZeroPointV = new VideoParameter(0, cp.ParseString("zero_v", null));
            MeterPointU = new VideoParameter(-0.25, cp.ParseString("meter_u", null));
            MeterPointV = new VideoParameter(0, cp.ParseString("meter_v", null));
            FiftyRatio = new VideoParameter(0.5, cp.ParseString("fifty", null));
            MeterScale = new VideoParameter(1, cp.ParseString("meter_scale", null));
            SpecialHint = new VideoParameter(0, cp.ParseString("special_hint", null));
        }

        public void Reset()
        {
            ResetAllIntrinsics();

            ZeroPointU = ZeroPointU.Reset().ToFileLevel();
            ZeroPointV = ZeroPointV.Reset().ToFileLevel();
            MeterPointU = MeterPointU.Reset().ToFileLevel();
            MeterPointV = MeterPointV.Reset().ToFileLevel();
            FiftyRatio = FiftyRatio.Reset().ToFileLevel();
            MeterScale = MeterScale.Reset().ToFileLevel();
            SpecialHint = SpecialHint.Reset();
        }
    }

    class BlindSpotParameters : PinholeIntrinsicsParameters, VideoParameters
    {
        public VideoParameter P00U { get; set; }
        public VideoParameter P00V { get; set; }
        public VideoParameter P04U { get; set; }
        public VideoParameter P04V { get; set; }
        public VideoParameter P30U { get; set; }
        public VideoParameter P30V { get; set; }
        public VideoParameter P34U { get; set; }
        public VideoParameter P34V { get; set; }
        public VideoParameter P0hRatio { get; set; }
        public VideoParameter P3hRatio { get; set; }
        public VideoParameter Ph0Ratio { get; set; }
        public VideoParameter Ph4Ratio { get; set; }
        public VideoParameter SpecialHint { get; set; } // 0: left, 1: right

        public BlindSpotParameters()
        {
            P00U = new VideoParameter(-0.25);
            P00V = new VideoParameter(0.25);
            P04U = new VideoParameter(-0.25);
            P04V = new VideoParameter(-0.25);
            P30U = new VideoParameter(0.25);
            P30V = new VideoParameter(0.25);
            P34U = new VideoParameter(0.25);
            P34V = new VideoParameter(-0.25);
            P0hRatio = new VideoParameter(0.5);
            P3hRatio = new VideoParameter(0.5);
            Ph0Ratio = new VideoParameter(0.5);
            Ph4Ratio = new VideoParameter(0.5);
            SpecialHint = new VideoParameter(0);

            P00U = P00U.ToFileLevel();
            P00V = P00V.ToFileLevel();
            P04U = P04U.ToFileLevel();
            P04V = P04V.ToFileLevel();
            P30U = P30U.ToFileLevel();
            P30V = P30V.ToFileLevel();
            P34U = P34U.ToFileLevel();
            P34V = P34V.ToFileLevel();
            P0hRatio = P0hRatio.ToFileLevel();
            P3hRatio = P3hRatio.ToFileLevel();
            Ph0Ratio = Ph0Ratio.ToFileLevel();
            Ph4Ratio = Ph4Ratio.ToFileLevel();
        }

        public SpecialCameraType GetSpecialType()
        {
            return SpecialHint.value == 1 ? SpecialCameraType.RightBlindSpotCamera : SpecialCameraType.LeftBlindSpotCamera;
        }

        public void ToXml(XmlElement node)
        {
            PinholeIntrinsicsToXml(node);

            var cw = new AttributeWriter(node);
            cw.WriteString("p00_u", P00U.ToString());
            cw.WriteString("p00_v", P00V.ToString());
            cw.WriteString("p04_u", P04U.ToString());
            cw.WriteString("p04_v", P04V.ToString());
            cw.WriteString("p30_u", P30U.ToString());
            cw.WriteString("p30_v", P30V.ToString());
            cw.WriteString("p34_u", P34U.ToString());
            cw.WriteString("p34_v", P34V.ToString());
            cw.WriteString("p0h", P0hRatio.ToString());
            cw.WriteString("p3h", P3hRatio.ToString());
            cw.WriteString("ph0", Ph0Ratio.ToString());
            cw.WriteString("ph4", Ph4Ratio.ToString());
            cw.WriteString("special_hint", SpecialHint.ToString());
        }

        public void FromXml(XmlElement node)
        {
            PinholeIntrinsicsFromXml(node);

            var cp = new AttributeParser(node);
            P00U = new VideoParameter(-0.25, cp.ParseString("p00_u", null));
            P00V = new VideoParameter(0.25, cp.ParseString("p00_v", null));
            P04U = new VideoParameter(-0.25, cp.ParseString("p04_u", null));
            P04V = new VideoParameter(-0.25, cp.ParseString("p04_v", null));
            P30U = new VideoParameter(0.25, cp.ParseString("p30_u", null));
            P30V = new VideoParameter(0.25, cp.ParseString("p30_v", null));
            P34U = new VideoParameter(0.25, cp.ParseString("p34_u", null));
            P34V = new VideoParameter(-0.25, cp.ParseString("p34_v", null));
            P0hRatio = new VideoParameter(0.5, cp.ParseString("p0h", null));
            P3hRatio = new VideoParameter(0.5, cp.ParseString("p3h", null));
            Ph0Ratio = new VideoParameter(0.5, cp.ParseString("ph0", null));
            Ph4Ratio = new VideoParameter(0.5, cp.ParseString("ph4", null));
            SpecialHint = new VideoParameter(0, cp.ParseString("special_hint", null));
        }

        public void Reset()
        {
            ResetAllIntrinsics();

            P00U = P00U.Reset().ToFileLevel();
            P00V = P00V.Reset().ToFileLevel();
            P04U = P04U.Reset().ToFileLevel();
            P04V = P04V.Reset().ToFileLevel();
            P30U = P30U.Reset().ToFileLevel();
            P30V = P30V.Reset().ToFileLevel();
            P34U = P34U.Reset().ToFileLevel();
            P34V = P34V.Reset().ToFileLevel();
            P0hRatio = P0hRatio.Reset().ToFileLevel();
            P3hRatio = P3hRatio.Reset().ToFileLevel();
            Ph0Ratio = Ph0Ratio.Reset().ToFileLevel();
            Ph4Ratio = Ph4Ratio.Reset().ToFileLevel();
            SpecialHint = SpecialHint.Reset();
        }
    }

    class FisheyeVideoParameters : VideoParameters
    {
        public VideoParameter Invert { get; set; }
        public VideoParameter VFlip { get; set; }
        public VideoParameter Undistort { get; set; }
        public VideoParameter HFOV { get; set; }
        public VideoParameter HFOVFisheye { get; set; }
        public VideoParameter AspectRatio { get; set; }
        public VideoParameter CU { get; set; }
        public VideoParameter CV { get; set; }
        public VideoParameter K1 { get; set; }
        public VideoParameter K2 { get; set; }
        public VideoParameter K3 { get; set; }
        public VideoParameter K4 { get; set; }

        public VideoParameter RectifyRoll { get; set; }
        public VideoParameter RectifyPitch { get; set; }
        public VideoParameter RectifyYaw { get; set; }
        public VideoParameter OX { get; set; }
        public VideoParameter OY { get; set; }
        public VideoParameter OZ { get; set; }
        public VideoParameter Yaw { get; set; }
        public VideoParameter Pitch { get; set; }
        public VideoParameter Roll { get; set; }
        public VideoParameter SpecialHint { get; set; } // 0: normal, 1: any, 2: front

        public FisheyeVideoParameters()
        {
            Invert = new VideoParameter(0);
            VFlip = new VideoParameter(0);
            Undistort = new VideoParameter(0);
            HFOV = new VideoParameter(120);
            HFOVFisheye = new VideoParameter(150);
            AspectRatio = new VideoParameter(1);
            CU = new VideoParameter(0);
            CV = new VideoParameter(0);
            K1 = new VideoParameter(0);
            K2 = new VideoParameter(0);
            K3 = new VideoParameter(0);
            K4 = new VideoParameter(0);

            RectifyRoll = new VideoParameter(0);
            RectifyPitch = new VideoParameter(0);
            RectifyYaw = new VideoParameter(0);
            OX = new VideoParameter(0);
            OY = new VideoParameter(0);
            OZ = new VideoParameter(1);
            Yaw = new VideoParameter(0);
            Pitch = new VideoParameter(0);
            Roll = new VideoParameter(0);
            SpecialHint = new VideoParameter(0);

            HFOVFisheye = HFOVFisheye.ToFileLevel();
            AspectRatio = AspectRatio.ToFileLevel();
            CU = CU.ToFileLevel();
            CV = CV.ToFileLevel();
            K1 = K1.ToFileLevel();
            K2 = K2.ToFileLevel();
            K3 = K3.ToFileLevel();
            K4 = K4.ToFileLevel();
        }

        public void ModifyAllIntrinsicsToFixedLevel()
        {
            Invert = Invert.ToFixedLevel();
            VFlip = VFlip.ToFixedLevel();
            Undistort = Undistort.ToFixedLevel();
            HFOV = HFOV.ToFixedLevel();
            HFOVFisheye = HFOVFisheye.ToFixedLevel();
            AspectRatio = AspectRatio.ToFixedLevel();
            CU = CU.ToFixedLevel();
            CV = CV.ToFixedLevel();
            K1 = K1.ToFixedLevel();
            K2 = K2.ToFixedLevel();
            K3 = K3.ToFixedLevel();
            K4 = K4.ToFixedLevel();
        }

        public void ResetAllIntrinsics()
        {
            Invert = Invert.Reset();
            VFlip = VFlip.Reset();
            Undistort = Undistort.Reset();
            HFOV = HFOV.Reset();
            HFOVFisheye = HFOVFisheye.Reset().ToFileLevel();
            AspectRatio = AspectRatio.Reset().ToFileLevel();
            CU = CU.Reset().ToFileLevel();
            CV = CV.Reset().ToFileLevel();
            K1 = K1.Reset().ToFileLevel();
            K2 = K2.Reset().ToFileLevel();
            K3 = K3.Reset().ToFileLevel();
            K4 = K4.Reset().ToFileLevel();
        }

        public bool IsUndistortNecessary()
        {
            return AspectRatio.value != 1 || K1.value != 0 || K2.value != 0 || K3.value != 0 || K4.value != 0 || HFOV.value != HFOVFisheye.value;
        }

        public double RectifiedYaw
        {
            get
            {
                if (Yaw.value >= -90 && Yaw.value <= 90) return 0;
                else if (Yaw.value > 90 || Yaw.value < -90) return 180;
                else return 0;
            }
        }

        public SpecialCameraType GetSpecialType()
        {
            switch ((int)SpecialHint.value)
            {
                case 0:
                    return SpecialCameraType.NormalFisheyeCamera;
                case 1:
                    return SpecialCameraType.AnyFisheyeCamera;
                case 2:
                    return SpecialCameraType.FrontFisheyeCamera;
                default:
                    return SpecialCameraType.NormalFisheyeCamera;
            }
        }

        public void ToXml(XmlElement node)
        {
            var cw = new AttributeWriter(node);

            cw.WriteString("inversion", Invert.ToString());
            cw.WriteString("vertical_flip", VFlip.ToString());
            var legacyInvert = new VideoParameter(Invert.defaultValue, (int)Invert.value ^ (int)VFlip.value, Invert.fix);
            cw.WriteString("invert", legacyInvert.ToString());
            cw.WriteString("vflip", VFlip.ToString());
            cw.WriteString("undistort", Undistort.ToString());
            cw.WriteString("hfov", HFOV.ToString());
            cw.WriteString("hfov_fisheye", HFOVFisheye.ToString());
            cw.WriteString("aspectratio", AspectRatio.ToString());
            cw.WriteString("cu", CU.ToString());
            cw.WriteString("cv", CV.ToString());
            cw.WriteString("k1", K1.ToString());
            cw.WriteString("k2", K2.ToString());
            cw.WriteString("k3", K3.ToString());
            cw.WriteString("k4", K4.ToString());

            cw.WriteString("rectify_roll", RectifyRoll.ToString());
            cw.WriteString("rectify_pitch", RectifyPitch.ToString());
            cw.WriteString("rectify_yaw", RectifyYaw.ToString());
            cw.WriteDouble("rectified_yaw", RectifiedYaw);
            cw.WriteString("ox", OX.ToString());
            cw.WriteString("oy", OY.ToString());
            cw.WriteString("oz", OZ.ToString());
            cw.WriteString("yaw", Yaw.ToString());
            cw.WriteString("pitch", Pitch.ToString());
            cw.WriteString("roll", Roll.ToString());
            cw.WriteString("special_hint", SpecialHint.ToString());
        }

        public void FromXml(XmlElement node)
        {
            var cp = new AttributeParser(node);

            if (node.Attributes["inversion"] != null)
            {
                Invert = new VideoParameter(0, cp.ParseString("inversion", null));
                VFlip = new VideoParameter(0, cp.ParseString("vertical_flip", null));
            }
            else
            {
                var rawInvert = new VideoParameter(0, cp.ParseString("invert", null));
                VFlip = new VideoParameter(0, cp.ParseString("vflip", null));
                Invert = new VideoParameter(rawInvert.defaultValue, (int)rawInvert.value ^ (int)VFlip.value, rawInvert.fix);
            }
            Undistort = new VideoParameter(0, cp.ParseString("undistort", null));
            HFOV = new VideoParameter(120, cp.ParseString("hfov", null));
            HFOVFisheye = new VideoParameter(150, cp.ParseString("hfov_fisheye", null));
            AspectRatio = new VideoParameter(1, cp.ParseString("aspectratio", null));
            CU = new VideoParameter(0, cp.ParseString("cu", null));
            CV = new VideoParameter(0, cp.ParseString("cv", null));
            K1 = new VideoParameter(0, cp.ParseString("k1", null));
            K2 = new VideoParameter(0, cp.ParseString("k2", null));
            K3 = new VideoParameter(0, cp.ParseString("k3", null));
            K4 = new VideoParameter(0, cp.ParseString("k4", null));

            RectifyRoll = new VideoParameter(0, cp.ParseString("rectify_roll", null));
            RectifyPitch = new VideoParameter(0, cp.ParseString("rectify_pitch", null));
            RectifyYaw = new VideoParameter(0, cp.ParseString("rectify_yaw", null));
            OX = new VideoParameter(0, cp.ParseString("ox", null));
            OY = new VideoParameter(0, cp.ParseString("oy", null));
            OZ = new VideoParameter(1, cp.ParseString("oz", null));
            Yaw = new VideoParameter(0, cp.ParseString("yaw", null));
            Pitch = new VideoParameter(0, cp.ParseString("pitch", null));
            Roll = new VideoParameter(0, cp.ParseString("roll", null));
            SpecialHint = new VideoParameter(0, cp.ParseString("special_hint", null));
        }

        public void Reset()
        {
            ResetAllIntrinsics();

            RectifyRoll = RectifyRoll.Reset();
            RectifyPitch = RectifyPitch.Reset();
            RectifyYaw = RectifyYaw.Reset();
            OX = OX.Reset();
            OY = OY.Reset();
            OZ = OZ.Reset();
            Yaw = Yaw.Reset();
            Pitch = Pitch.Reset();
            Roll = Roll.Reset();
            SpecialHint = SpecialHint.Reset();
        }
    }

    class CustomizedMap
    {
        public float[] USampleCoords { get; set; } // -0.5~+0.5
        public float[] VSampleCoords { get; set; }
        public float[,] UMap { get; set; } // [v, u]
        public float[,] VMap { get; set; } // [v, u]

        public CustomizedMap(int uSampleCount, int vSampleCount)
        {
            USampleCoords = new float[uSampleCount];
            VSampleCoords = new float[vSampleCount];
            UMap = new float[vSampleCount, uSampleCount];
            VMap = new float[vSampleCount, uSampleCount];
        }
    }

    class CustomizedParameters : VideoParameters
    {
        public VideoParameter Invert { get; set; }
        public VideoParameter VFlip { get; set; }
        public VideoParameter Undistort { get; set; }
        public VideoParameter HFOV { get; set; }
        public CustomizedMap Map { get; set; }

        public VideoParameter RectifyRoll { get; set; }
        public VideoParameter RectifyPitch { get; set; }
        public VideoParameter RectifyYaw { get; set; }
        public VideoParameter OX { get; set; }
        public VideoParameter OY { get; set; }
        public VideoParameter OZ { get; set; }
        public VideoParameter Yaw { get; set; }
        public VideoParameter Pitch { get; set; }
        public VideoParameter Roll { get; set; }

        public CustomizedParameters()
        {
            Invert = new VideoParameter(0);
            VFlip = new VideoParameter(0);
            Undistort = new VideoParameter(0);
            HFOV = new VideoParameter(120);
            Map = null;

            RectifyRoll = new VideoParameter(0);
            RectifyPitch = new VideoParameter(0);
            RectifyYaw = new VideoParameter(0);
            OX = new VideoParameter(0);
            OY = new VideoParameter(0);
            OZ = new VideoParameter(1);
            Yaw = new VideoParameter(0);
            Pitch = new VideoParameter(0);
            Roll = new VideoParameter(0);
        }

        public void ModifyAllIntrinsicsToFixedLevel()
        {
            Invert = Invert.ToFixedLevel();
            VFlip = VFlip.ToFixedLevel();
            Undistort = Undistort.ToFixedLevel();
            HFOV = HFOV.ToFixedLevel();
        }

        public void ResetAllIntrinsics()
        {
            Invert = Invert.Reset();
            VFlip = VFlip.Reset();
            Undistort = Undistort.Reset();
            HFOV = HFOV.Reset();
            Map = null;
        }

        public bool IsUndistortNecessary()
        {
            return Map != null;
        }

        public double RectifiedYaw
        {
            get
            {
                if (Yaw.value >= -90 && Yaw.value <= 90) return 0;
                else if (Yaw.value > 90 || Yaw.value < -90) return 180;
                else return 0;
            }
        }

        public SpecialCameraType GetSpecialType()
        {
            return SpecialCameraType.CustomizedCamera;
        }

        public void ToXml(XmlElement node)
        {
            var cw = new AttributeWriter(node);

            cw.WriteString("inversion", Invert.ToString());
            cw.WriteString("vertical_flip", VFlip.ToString());
            cw.WriteString("undistort", Undistort.ToString());
            cw.WriteString("hfov", HFOV.ToString());

            cw.WriteString("rectify_roll", RectifyRoll.ToString());
            cw.WriteString("rectify_pitch", RectifyPitch.ToString());
            cw.WriteString("rectify_yaw", RectifyYaw.ToString());
            cw.WriteDouble("rectified_yaw", RectifiedYaw);
            cw.WriteString("ox", OX.ToString());
            cw.WriteString("oy", OY.ToString());
            cw.WriteString("oz", OZ.ToString());
            cw.WriteString("yaw", Yaw.ToString());
            cw.WriteString("pitch", Pitch.ToString());
            cw.WriteString("roll", Roll.ToString());

            if (Map != null)
            {
                var mapNode = node.AppendChild(node.OwnerDocument.CreateElement("map")) as XmlElement;
                mapNode.Attributes.Append(node.OwnerDocument.CreateAttribute("u_coords")).Value = String.Join(',', Map.USampleCoords);
                mapNode.Attributes.Append(node.OwnerDocument.CreateAttribute("v_coords")).Value = String.Join(',', Map.VSampleCoords);
                float[] uMapArray = new float[Map.UMap.GetLength(0) * Map.UMap.GetLength(1)], vMapArray = new float[Map.VMap.GetLength(0) * Map.VMap.GetLength(1)];
                Buffer.BlockCopy(Map.UMap, 0, uMapArray, 0, uMapArray.Length * sizeof(float));
                Buffer.BlockCopy(Map.VMap, 0, vMapArray, 0, vMapArray.Length * sizeof(float));
                mapNode.InnerText = String.Join(',', uMapArray) + ";" + String.Join(',', vMapArray);
            }
        }

        public void FromXml(XmlElement node)
        {
            var cp = new AttributeParser(node);

            Invert = new VideoParameter(0, cp.ParseString("inversion", null));
            VFlip = new VideoParameter(0, cp.ParseString("vertical_flip", null));
            Undistort = new VideoParameter(0, cp.ParseString("undistort", null));
            HFOV = new VideoParameter(120, cp.ParseString("hfov", null));

            RectifyRoll = new VideoParameter(0, cp.ParseString("rectify_roll", null));
            RectifyPitch = new VideoParameter(0, cp.ParseString("rectify_pitch", null));
            RectifyYaw = new VideoParameter(0, cp.ParseString("rectify_yaw", null));
            OX = new VideoParameter(0, cp.ParseString("ox", null));
            OY = new VideoParameter(0, cp.ParseString("oy", null));
            OZ = new VideoParameter(1, cp.ParseString("oz", null));
            Yaw = new VideoParameter(0, cp.ParseString("yaw", null));
            Pitch = new VideoParameter(0, cp.ParseString("pitch", null));
            Roll = new VideoParameter(0, cp.ParseString("roll", null));

            Map = null;
            var mapNodes = node.GetElementsByTagName("map");
            if (mapNodes.Count > 0)
            {
                var mapNode = mapNodes[0];
                var uCoordsElems = mapNode.Attributes["u_coords"].Value.Split(',');
                var vCoordsElems = mapNode.Attributes["v_coords"].Value.Split(',');
                var mapComps = mapNode.InnerText.Split(';');
                var uMapElems = mapComps[0].Split(',');
                var vMapElems = mapComps[1].Split(',');
                if (uCoordsElems.Length > 0 && vCoordsElems.Length > 0 && 
                    uMapElems.Length == uCoordsElems.Length * vCoordsElems.Length &&
                    vMapElems.Length == uCoordsElems.Length * vCoordsElems.Length)
                {
                    Map = new CustomizedMap(uCoordsElems.Length, vCoordsElems.Length);
                    for (int i = 0; i < Map.USampleCoords.Length; i++) Map.USampleCoords[i] = Convert.ToSingle(uCoordsElems[i]);
                    for (int i = 0; i < Map.VSampleCoords.Length; i++) Map.VSampleCoords[i] = Convert.ToSingle(vCoordsElems[i]);
                    int k = 0;
                    for (int v = 0; v < vCoordsElems.Length; v++)
                    {
                        for (int u = 0; u < uCoordsElems.Length; u++)
                        {
                            Map.UMap[v, u] = Convert.ToSingle(uMapElems[k]);
                            Map.VMap[v, u] = Convert.ToSingle(vMapElems[k]);
                            k++;
                        }
                    }
                }
            }
        }

        public void Reset()
        {
            ResetAllIntrinsics();

            RectifyRoll = RectifyRoll.Reset();
            RectifyPitch = RectifyPitch.Reset();
            RectifyYaw = RectifyYaw.Reset();
            OX = OX.Reset();
            OY = OY.Reset();
            OZ = OZ.Reset();
            Yaw = Yaw.Reset();
            Pitch = Pitch.Reset();
            Roll = Roll.Reset();
        }
    }

    class VideoChannelConfig
    {
        public VideoChannelConfig()
        {
            Name = "Unknown";
            Parameters = new NormalVideoParameters();
        }

        public String Name { get; set; }

        /*
         * special type (C#)    -> parameters (C#)         -> meta protocol (C++) -> meta protocol (C#)
         * ----------------------------------------------------------------------------------------------
         * Normal               -> NormalVideoParameters   -> default-v2          -> DefaultCameraMeta
         * AnyCamera            -> NormalVideoParameters   -> generic-v2          -> GenericCameraMeta
         * FrontCamera          -> NormalVideoParameters   -> generic-v2          -> GenericCameraMeta
         * LeftLaneCamera       -> LaneLineVideoParameters -> line-v3             -> LaneLineCameraMeta
         * RightLaneCamera      -> LaneLineVideoParameters -> line-v3             -> LaneLineCameraMeta
         * LeftBlindSpotCamera  -> BlindSpotParameters     -> bs-v3               -> BlindSpotCameraMeta
         * RightBlindSpotCamera -> BlindSpotParameters     -> bs-v3               -> BlindSpotCameraMeta
         * NormalFisheyeCamera  -> FisheyeVideoParameters  -> fisheye-v1          -> FisheyeCameraMeta
         * FrontFisheyeCamera   -> FisheyeVideoParameters  -> fisheye-v1          -> FisheyeCameraMeta
         * AnyFisheyeCamera     -> FisheyeVideoParameters  -> fisheye-v1          -> FisheyeCameraMeta
         * CustomizedCamera     -> CustomizedParameters    -> customized-v1       -> CustomizedCameraMeta
         */
        public VideoParameters Parameters { get; set; }

        public IntSize RemoteSize { get; set; } // deprecated
        public VideoDataCodec RemoteCodec { get; set; } // deprecated
        public int RemoteRecordFPS { get; set; } // deprecated
        public bool RemoteAlignedFPS { get; set; } // deprecated
    }

    class VideoConfig
    {
        public VideoConfig()
        {
            SetConfig(Xml.Create("root").InnerXml);
        }

        public static VideoConfig Load(object caller)
        {
            var config = new VideoConfig();
            var configString = Agency.GetModuleConfig(caller, "video.system");
            config.SetConfig(configString);
            return config;
        }

        public void Save(object caller)
        {
            Agency.SetModuleConfig(caller, "video.system", GetConfig());
        }

        public void SetConfig(String config)
        {
            if (String.IsNullOrEmpty(config)) return;
            
            var xml = new XmlDocument();
            xml.LoadXml(config);
            var rootNode = xml.DocumentElement;

            var rootCP = new AttributeParser(rootNode);
            HiresBufferMaxSize = rootCP.ParseLong("hires_buffer_max_size", 500000000);
            MaxProcessThreadCount = Math.Max(1, Math.Min(Environment.ProcessorCount / 2, rootCP.ParseInt("max_process_thread_count", 4)));

            var deviceNodes = rootNode.GetElementsByTagName("device");
            Devices = new List<VideoDeviceConfig>();
            foreach (XmlElement node in deviceNodes)
            {
                var cp = new AttributeParser(node);
                var dev = new VideoDeviceConfig();
                dev.ID = new VideoDeviceID();
                dev.ID.Type = cp.ParseString("cam_type", null);

                int dummy;
                if (dev.ID.Type == null || Int32.TryParse(dev.ID.Type, out dummy)) continue;

                dev.ID.LocalID = cp.ParseString("local_id", null);
                if (dev.ID.LocalID == null || dev.ID.LocalID.Length == 0) continue;

                int mode = cp.ParseInt("mode", 0);
                dev.Mode = (VideoDeviceMode)Math.Max(0, mode);

                var legacyEnabled = cp.ParseBool("enabled", "yes", false);
                if (dev.Mode == VideoDeviceMode.Disabled && legacyEnabled) dev.Mode = VideoDeviceMode.Input;

                dev.ChannelIndex = Math.Max(0, Math.Min(23, cp.ParseInt("channel", 0)));
                var fpsInt = cp.ParseInt("fps", 15);
                if (fpsInt == 0)
                {
                    dev.FrameRate = 15;
                    dev.AlignedFPS = false;
                }
                else
                {
                    dev.FrameRate = Math.Max(10, Math.Min(120, fpsInt));
                    dev.AlignedFPS = cp.ParseBool("aligned_fps", "yes", false);
                }

                var inputCodec = (VideoDataCodec)cp.ParseInt("input_codec", (int)VideoDataCodec.Invalid);
                if (inputCodec != VideoDataCodec.Invalid)
                {
                    dev.InputMode = new VideoInputMode()
                    {
                        InputCodec = inputCodec,
                        Size = new IntSize(cp.ParseInt("input_width", 0), cp.ParseInt("input_height", 0)),
                    };
                    if (dev.InputMode.Size.Width == 0 || dev.InputMode.Size.Height == 0) continue;
                }

                var outputCodec = (VideoDataCodec)cp.ParseInt("output_codec", (int)VideoDataCodec.Invalid);
                if (outputCodec != VideoDataCodec.Invalid)
                {
                    dev.OutputMode = new VideoOutputMode()
                    {
                        OutputCodec = outputCodec,
                        Size = new IntSize(cp.ParseInt("output_width", 0), cp.ParseInt("output_height", 0)),
                    };
                    if (dev.OutputMode.Size.Width == 0 || dev.OutputMode.Size.Height == 0) continue;
                }

                if (Devices.Exists(d => d.ID.Equals(dev.ID))) continue;

                Devices.Add(dev);
            }

            Channels = new VideoChannelConfig[24];
            var oldStyle = new bool[24];
            for (int i = 0; i < 24; i++)
            {
                oldStyle[i] = true;

                Channels[i] = new VideoChannelConfig();
                Channels[i].Name = "Channel " + (char)('A' + i);

                var chNodes = rootNode.GetElementsByTagName("ch" + i);
                if (chNodes.Count == 0) continue;

                var chNode = chNodes[0] as XmlElement;
                var cp = new AttributeParser(chNode);

                var fpsInt = cp.ParseInt("remote_rec_fps", 15);
                if (fpsInt == 0)
                {
                    Channels[i].RemoteRecordFPS = 15;
                    Channels[i].RemoteAlignedFPS = false;
                }
                else
                {
                    Channels[i].RemoteRecordFPS = Math.Max(10, Math.Min(120, fpsInt));
                    Channels[i].RemoteAlignedFPS = cp.ParseBool("remote_aligned_fps", "yes", false);
                }

                Channels[i].RemoteCodec = (VideoDataCodec)cp.ParseInt("remote_codec", (int)VideoDataCodec.Invalid);
                if (Channels[i].RemoteCodec != VideoDataCodec.Invalid)
                {
                    var width = cp.ParseInt("remote_width", 0);
                    var height = cp.ParseInt("remote_height", 0);
                    if (width <= 0 || height <= 0) Channels[i].RemoteCodec = VideoDataCodec.Invalid;
                    Channels[i].RemoteSize = new IntSize(width, height);
                }

                var paramNodes = chNode.GetElementsByTagName("param");
                if (paramNodes.Count == 0) continue;

                var type = cp.ParseString("param_type", null);
                if (type == null) continue;

                Channels[i].Name = cp.ParseString("name", Channels[i].Name);

                var paramNode = paramNodes[0] as XmlElement;
                switch (type)
                {
                    case "line":
                        Channels[i].Parameters = new LaneLineVideoParameters();
                        Channels[i].Parameters.FromXml(paramNode);
                        break;
                    case "bs":
                        Channels[i].Parameters = new BlindSpotParameters();
                        Channels[i].Parameters.FromXml(paramNode);
                        break;
                    case "fisheye":
                        Channels[i].Parameters = new FisheyeVideoParameters();
                        Channels[i].Parameters.FromXml(paramNode);
                        break;
                    case "custom":
                        Channels[i].Parameters = new CustomizedParameters();
                        Channels[i].Parameters.FromXml(paramNode);
                        break;
                    default: // "normal"
                        Channels[i].Parameters = new NormalVideoParameters();
                        Channels[i].Parameters.FromXml(paramNode);
                        break;
                }

                oldStyle[i] = false;
            }

            // The following is for compatibility / 以下为向后兼容部分
            var oldStyleSpecialType = new SpecialCameraType[24];
            var frontCameraNodes = rootNode.GetElementsByTagName("front_camera");
            if (frontCameraNodes.Count > 0)
            {
                var cp = new AttributeParser(frontCameraNodes[0] as XmlElement);
                int frontCameraChannel = cp.ParseInt("channel", -1);
                if (frontCameraChannel >= 0 && frontCameraChannel < oldStyleSpecialType.Length) oldStyleSpecialType[frontCameraChannel] = SpecialCameraType.FrontCamera;

            }
            var leftLaneCameraNodes = rootNode.GetElementsByTagName("left_lane_camera");
            if (leftLaneCameraNodes.Count > 0)
            {
                var cp = new AttributeParser(leftLaneCameraNodes[0] as XmlElement);
                int leftLaneCameraChannel = cp.ParseInt("channel", -1);
                if (leftLaneCameraChannel >= 0 && leftLaneCameraChannel < oldStyleSpecialType.Length) oldStyleSpecialType[leftLaneCameraChannel] = SpecialCameraType.LeftLaneCamera;
            }
            var rightLaneCameraNodes = rootNode.GetElementsByTagName("right_lane_camera");
            if (rightLaneCameraNodes.Count > 0)
            {
                var cp = new AttributeParser(rightLaneCameraNodes[0] as XmlElement);
                int rightLaneCameraChannel = cp.ParseInt("channel", -1);
                if (rightLaneCameraChannel >= 0 && rightLaneCameraChannel < oldStyleSpecialType.Length) oldStyleSpecialType[rightLaneCameraChannel] = SpecialCameraType.RightLaneCamera;
            }
            var leftBSCameraNodes = rootNode.GetElementsByTagName("left_bs_camera");
            if (leftBSCameraNodes.Count > 0)
            {
                var cp = new AttributeParser(leftBSCameraNodes[0] as XmlElement);
                int leftBSCameraChannel = cp.ParseInt("channel", -1);
                if (leftBSCameraChannel >= 0 && leftBSCameraChannel < oldStyleSpecialType.Length) oldStyleSpecialType[leftBSCameraChannel] = SpecialCameraType.LeftBlindSpotCamera;
            }
            var rightBSCameraNodes = rootNode.GetElementsByTagName("right_bs_camera");
            if (rightBSCameraNodes.Count > 0)
            {
                var cp = new AttributeParser(rightBSCameraNodes[0] as XmlElement);
                int rightBSCameraChannel = cp.ParseInt("channel", -1);
                if (rightBSCameraChannel >= 0 && rightBSCameraChannel < oldStyleSpecialType.Length) oldStyleSpecialType[rightBSCameraChannel] = SpecialCameraType.RightBlindSpotCamera;
            }

            for (int i = 0; i < 24; i++)
            {
                if (!oldStyle[i]) continue;

                Channels[i] = new VideoChannelConfig();
                Channels[i].Name = "Channel " + (char)('A' + i);

                var chNodes = rootNode.GetElementsByTagName("ch" + i);
                if (chNodes.Count == 0) continue;

                var chNode = chNodes[0] as XmlElement;

                var cpMain = new AttributeParser(chNode);
                Channels[i].Name = cpMain.ParseString("name", Channels[i].Name);

                bool invert = false, vflip = false;
                if (chNode.Attributes["rotate"] == null)
                {
                    invert = cpMain.ParseBool("flip", "yes", false);
                    vflip = false;
                }
                else
                {
                    var rawInvert = cpMain.ParseBool("rotate", "yes", false);
                    vflip = cpMain.ParseBool("vflip", "yes", false);
                    invert = rawInvert ^ vflip;
                }

                bool undistort = cpMain.ParseBool("undistort", "yes", false);

                double hfov = 45, asp = 1, cu = 0, cv = 0, k1 = 0, k2 = 0, k3 = 0, k4 = 0, k5 = 0, k6 = 0, p1 = 0, p2 = 0;
                var cipNodes = chNode.GetElementsByTagName("cip");
                if (cipNodes.Count > 0)
                {
                    var cipParser = new AttributeParser(cipNodes[0] as XmlElement);
                    hfov = cipParser.ParseDouble("hfov", 45);
                    asp = cipParser.ParseDouble("aspect_ratio", 1);
                    cu = cipParser.ParseDouble("cu", 0);
                    cv = cipParser.ParseDouble("cv", 0);
                    k1 = cipParser.ParseDouble("k1", 0);
                    k2 = cipParser.ParseDouble("k2", 0);
                    p1 = cipParser.ParseDouble("p1", 0);
                    p2 = cipParser.ParseDouble("p2", 0);
                }

                var specialType = oldStyleSpecialType[i];
                if (specialType == SpecialCameraType.Normal || specialType == SpecialCameraType.FrontCamera)
                {
                    var param = new NormalVideoParameters();
                    param.Invert = param.Invert.ManualSet(invert ? 1 : 0);
                    param.VFlip = param.VFlip.ManualSet(vflip ? 1 : 0);
                    param.Undistort = param.Undistort.ManualSet(undistort ? 1 : 0);
                    param.HFOV = param.HFOV.ManualSet(hfov);
                    param.AspectRatio = param.AspectRatio.FileSet(asp);
                    param.CU = param.CU.FileSet(cu);
                    param.CV = param.CV.FileSet(cv);
                    param.K1 = param.K1.FileSet(k1);
                    param.K2 = param.K2.FileSet(k2);
                    param.K3 = param.K3.FileSet(k3);
                    param.K4 = param.K4.FileSet(k4);
                    param.K5 = param.K5.FileSet(k5);
                    param.K6 = param.K6.FileSet(k6);
                    param.P1 = param.P1.FileSet(p1);
                    param.P2 = param.P2.FileSet(p2);

                    param.SpecialHint = param.SpecialHint.ManualSet(specialType == SpecialCameraType.FrontCamera ? 2 : 0);

                    if (specialType == SpecialCameraType.FrontCamera)
                    {
                        var cpSpecial = new AttributeParser(frontCameraNodes[0] as XmlElement);
                        param.OX = param.OX.ManualSet(cpSpecial.ParseDouble("offset", -1.5));
                        param.OY = param.OY.ManualSet(cpSpecial.ParseDouble("offset_y", 0));
                        param.OZ = param.OZ.ManualSet(cpSpecial.ParseDouble("height", 1.0));
                        var autocalib = cpSpecial.ParseBool("enable_auto_calib", "yes", true);
                        if (!autocalib)
                        {
                            var centerX = cpSpecial.ParseDouble("fixed_center_x", 0);
                            var centerY = cpSpecial.ParseDouble("fixed_center_y", 0);
                            param.Yaw = param.Yaw.ToFixedLevel(-Math.Atan(centerX * 2 * Math.Tan(0.5 * param.HFOV.value * Math.PI / 180)) * 180 / Math.PI);
                            param.Pitch = param.Pitch.ToFixedLevel(Math.Atan(centerY * 2 * Math.Tan(0.5 * param.HFOV.value * Math.PI / 180)) * 180 / Math.PI);

                            param.ModifyAllIntrinsicsToFixedLevel();
                            param.SpecialHint = param.SpecialHint.ToFixedLevel();
                        }
                    }

                    Channels[i].Parameters = param;
                }
                else if (specialType == SpecialCameraType.LeftLaneCamera || specialType == SpecialCameraType.RightLaneCamera)
                {
                    var param = new LaneLineVideoParameters();
                    param.Invert = param.Invert.ToFixedLevel(invert ? 1 : 0);
                    param.VFlip = param.VFlip.ToFixedLevel(vflip ? 1 : 0);
                    param.Undistort = param.Undistort.ToFixedLevel(undistort ? 1 : 0);
                    param.HFOV = param.HFOV.ToFixedLevel(hfov);
                    param.AspectRatio = param.AspectRatio.ToFixedLevel(asp);
                    param.CU = param.CU.ToFixedLevel(cu);
                    param.CV = param.CV.ToFixedLevel(cv);
                    param.K1 = param.K1.ToFixedLevel(k1);
                    param.K2 = param.K2.ToFixedLevel(k2);
                    param.K3 = param.K3.ToFixedLevel(k3);
                    param.K4 = param.K4.ToFixedLevel(k4);
                    param.K5 = param.K5.ToFixedLevel(k5);
                    param.K6 = param.K6.ToFixedLevel(k6);
                    param.P1 = param.P1.ToFixedLevel(p1);
                    param.P2 = param.P2.ToFixedLevel(p2);

                    var cpSpecial = new AttributeParser((specialType == SpecialCameraType.LeftLaneCamera ? leftLaneCameraNodes[0] : rightLaneCameraNodes[0]) as XmlElement);
                    var zero = cpSpecial.ParsePoint("zero", new FloatPoint(0.25f, 0.0f)).Value;
                    var meter = cpSpecial.ParsePoint("meter", new FloatPoint(-0.25f, 0.0f)).Value;
                    var fifty = cpSpecial.ParseDouble("ratio", 0.5);
                    param.ZeroPointU = param.ZeroPointU.ToFixedLevel(zero.X);
                    param.ZeroPointV = param.ZeroPointV.ToFixedLevel(zero.Y);
                    param.MeterPointU = param.MeterPointU.ToFixedLevel(meter.X);
                    param.MeterPointV = param.MeterPointV.ToFixedLevel(meter.Y);
                    param.FiftyRatio = param.FiftyRatio.ToFixedLevel(fifty);
                    param.MeterScale = param.MeterScale.ToFixedLevel(1);
                    param.SpecialHint = param.SpecialHint.ToFixedLevel(specialType == SpecialCameraType.LeftLaneCamera ? 0 : 1);

                    Channels[i].Parameters = param;
                }
                else if (specialType == SpecialCameraType.LeftBlindSpotCamera || specialType == SpecialCameraType.RightBlindSpotCamera)
                {
                    var param = new BlindSpotParameters();
                    param.Invert = param.Invert.ToFixedLevel(invert ? 1 : 0);
                    param.VFlip = param.VFlip.ToFixedLevel(vflip ? 1 : 0);
                    param.Undistort = param.Undistort.ToFixedLevel(undistort ? 1 : 0);
                    param.HFOV = param.HFOV.ToFixedLevel(hfov);
                    param.AspectRatio = param.AspectRatio.ToFixedLevel(asp);
                    param.CU = param.CU.ToFixedLevel(cu);
                    param.CV = param.CV.ToFixedLevel(cv);
                    param.K1 = param.K1.ToFixedLevel(k1);
                    param.K2 = param.K2.ToFixedLevel(k2);
                    param.K3 = param.K3.ToFixedLevel(k3);
                    param.K4 = param.K4.ToFixedLevel(k4);
                    param.K5 = param.K5.ToFixedLevel(k5);
                    param.K6 = param.K6.ToFixedLevel(k6);
                    param.P1 = param.P1.ToFixedLevel(p1);
                    param.P2 = param.P2.ToFixedLevel(p2);

                    var cpSpecial = new AttributeParser((specialType == SpecialCameraType.LeftBlindSpotCamera ? leftBSCameraNodes[0] : rightBSCameraNodes[0]) as XmlElement);
                    var p00 = cpSpecial.ParsePoint("p00", new FloatPoint(-0.25f, 0.25f)).Value;
                    var p04 = cpSpecial.ParsePoint("p04", new FloatPoint(-0.25f, -0.25f)).Value;
                    var p30 = cpSpecial.ParsePoint("p30", new FloatPoint(0.25f, 0.25f)).Value;
                    var p34 = cpSpecial.ParsePoint("p34", new FloatPoint(0.25f, -0.25f)).Value;
                    var p0hRatio = cpSpecial.ParseDouble("p0h_ratio", 0.5);
                    var p3hRatio = cpSpecial.ParseDouble("p3h_ratio", 0.5);
                    var ph0Ratio = cpSpecial.ParseDouble("ph0_ratio", 0.5);
                    var ph4Ratio = cpSpecial.ParseDouble("ph4_ratio", 0.5);
                    param.P00U = param.P00U.ToFixedLevel(p00.X);
                    param.P00V = param.P00V.ToFixedLevel(p00.Y);
                    param.P04U = param.P04U.ToFixedLevel(p04.X);
                    param.P04V = param.P04V.ToFixedLevel(p04.Y);
                    param.P30U = param.P30U.ToFixedLevel(p30.X);
                    param.P30V = param.P30V.ToFixedLevel(p30.Y);
                    param.P34U = param.P34U.ToFixedLevel(p34.X);
                    param.P34V = param.P34V.ToFixedLevel(p34.Y);
                    param.P0hRatio = param.P0hRatio.ToFixedLevel(p0hRatio);
                    param.P3hRatio = param.P3hRatio.ToFixedLevel(p3hRatio);
                    param.Ph0Ratio = param.Ph0Ratio.ToFixedLevel(ph0Ratio);
                    param.Ph4Ratio = param.Ph4Ratio.ToFixedLevel(ph4Ratio);
                    param.SpecialHint = param.SpecialHint.ToFixedLevel(specialType == SpecialCameraType.LeftBlindSpotCamera ? 0 : 1);

                    Channels[i].Parameters = param;
                }
            }
        }

        public String GetConfig()
        {
            var xml = Xml.Create("root");
            var rootNode = xml.DocumentElement;

            var rootCW = new AttributeWriter(rootNode);
            rootCW.WriteLong("hires_buffer_max_size", HiresBufferMaxSize);
            rootCW.WriteInt("max_process_thread_count", MaxProcessThreadCount);

            foreach (var dev in Devices)
            {
                var cw = new AttributeWriter(rootNode.AppendChild(xml.CreateElement("device")) as XmlElement);
                cw.WriteString("cam_type", dev.ID.Type);
                cw.WriteString("local_id", dev.ID.LocalID);
                cw.WriteInt("mode", (int)dev.Mode);
                cw.WriteBool("enabled", dev.Mode == VideoDeviceMode.Input, "yes", "no");
                cw.WriteInt("channel", dev.ChannelIndex);
                cw.WriteInt("fps", dev.FrameRate);
                cw.WriteBool("aligned_fps", dev.AlignedFPS, "yes", "no");
                if (dev.InputMode.InputCodec != VideoDataCodec.Invalid)
                {
                    cw.WriteInt("input_codec", (int)dev.InputMode.InputCodec);
                    cw.WriteInt("input_width", dev.InputMode.Size.Width);
                    cw.WriteInt("input_height", dev.InputMode.Size.Height);
                }
                if (dev.OutputMode.OutputCodec != VideoDataCodec.Invalid)
                {
                    cw.WriteInt("output_codec", (int)dev.OutputMode.OutputCodec);
                    cw.WriteInt("output_width", dev.OutputMode.Size.Width);
                    cw.WriteInt("output_height", dev.OutputMode.Size.Height);
                }
            }

            for (int i = 0; i < 24; i++)
            {
                var chNode = rootNode.AppendChild(xml.CreateElement("ch" + i)) as XmlElement;
                var cw = new AttributeWriter(chNode);
                cw.WriteString("name", Channels[i].Name);
                if (Channels[i].Parameters is NormalVideoParameters) cw.WriteString("param_type", "normal");
                else if (Channels[i].Parameters is LaneLineVideoParameters) cw.WriteString("param_type", "line");
                else if (Channels[i].Parameters is BlindSpotParameters) cw.WriteString("param_type", "bs");
                else if (Channels[i].Parameters is FisheyeVideoParameters) cw.WriteString("param_type", "fisheye");
                else if (Channels[i].Parameters is CustomizedParameters) cw.WriteString("param_type", "custom");
                Channels[i].Parameters.ToXml(chNode.AppendChild(xml.CreateElement("param")) as XmlElement);
                cw.WriteInt("remote_rec_fps", Channels[i].RemoteRecordFPS);
                cw.WriteBool("remote_aligned_fps", Channels[i].RemoteAlignedFPS, "yes", "no");
                cw.WriteInt("remote_codec", (int)Channels[i].RemoteCodec);
                cw.WriteInt("remote_width", Channels[i].RemoteSize.Width);
                cw.WriteInt("remote_height", Channels[i].RemoteSize.Height);
            }

            return xml.InnerXml;
        }

        public ConfigStatus GetConfigStatus()
        {
            String dummy;
            return GetConfigStatus(out dummy);
        }

        public ConfigStatus GetConfigStatus(out String errorHint)
        {
            errorHint = null;
            
            var appMode = Agency.GetAppMode();
            if (appMode == ApplicationMode.Replay || appMode == ApplicationMode.Offline || appMode == ApplicationMode.Remote)
            {
                return ConfigStatus.Enabled;
            }
            else if (appMode == ApplicationMode.Online)
            {
                var devices = Agency.GetVideoDevices();
                if (devices == null) return ConfigStatus.Disabled;

                bool hasError = false, hasOK = false;
                foreach (var dev in Devices)
                {
                    if (dev.ID.Type.Length == 0) continue;
                    if (dev.Mode == VideoDeviceMode.Disabled) continue;
                    if (!IsDeviceChannelOK(dev.ID))
                    {
                        errorHint = "Device channel already used: " + dev.ID.ToString();
                        hasError = true;
                    }
                    if (devices.ContainsKey(dev.ID)) hasOK = true;
                    else
                    {
                        errorHint = "Device unavailable: " + dev.ID.ToString();
                        hasError = true;
                    }
                }

                if (hasOK)
                {
                    if (hasError) return ConfigStatus.EnabledWithWarning;
                    else return ConfigStatus.Enabled;
                }
                else
                {
                    if (hasError) return ConfigStatus.EnabledWithError;
                    else return ConfigStatus.Disabled;
                }
            }
            else return ConfigStatus.Disabled;
        }

        public void DisableAll()
        {
            foreach (var dev in Devices)
            {
                dev.Mode = VideoDeviceMode.Disabled;
            }
            foreach (var ch in Channels)
            {
                ch.RemoteCodec = VideoDataCodec.Invalid;
            }
        }

        public void DisableErrorPart()
        {
            var devices = Agency.GetVideoDevices();
            foreach (var dev in Devices)
            {
                if (!IsDeviceChannelOK(dev.ID)) dev.Mode = VideoDeviceMode.Disabled;
                if (devices != null && !devices.ContainsKey(dev.ID)) dev.Mode = VideoDeviceMode.Disabled;
            }
        }

        public bool IsDeviceChannelOK(VideoDeviceID config)
        {
            var flags = new bool[24];
            foreach (var c in Devices)
            {
                if (c.ChannelIndex < 0 || c.ChannelIndex >= 24) continue;
                if (c.Mode == VideoDeviceMode.Disabled) continue;
                if (c.ID.Equals(config))
                {
                    return !flags[c.ChannelIndex];
                }
                else
                {
                    flags[c.ChannelIndex] = true;
                }
            }
            return false;
        }

        public void SetChannelDevices(VideoChannelDevice[] devices)
        {
            if (devices.Length != 24) return;

            var deviceConfigs = new List<VideoDeviceConfig>();
            var channels = new VideoChannelConfig[24];
            for (int i = 0; i < 24; i++)
            {
                channels[i] = new VideoChannelConfig();

                if (devices[i] == null) continue;
                if (String.IsNullOrEmpty(devices[i].Name)) continue;
                if (devices[i].ID == null) continue;
                if (devices[i].Codec == VideoDataCodec.Invalid) continue;
                if (devices[i].Size.Width <= 0 || devices[i].Size.Height <= 0) continue;
                if (devices[i].HFOV <= 0) continue;

                var devConfig = new VideoDeviceConfig();
                devConfig.ID = devices[i].ID;
                devConfig.Mode = devices[i].IsOutput ? VideoDeviceMode.Output : VideoDeviceMode.Input;
                devConfig.ChannelIndex = i;
                devConfig.FrameRate = devices[i].FrameRate;
                devConfig.AlignedFPS = false;
                if (devices[i].IsOutput) devConfig.OutputMode = new VideoOutputMode{ OutputCodec = devices[i].Codec, Size = devices[i].Size };
                else devConfig.InputMode = new VideoInputMode{ InputCodec = devices[i].Codec, Size = devices[i].Size };
                deviceConfigs.Add(devConfig);

                channels[i].Name = devices[i].Name;
                
                var param = channels[i].Parameters as NormalVideoParameters;
                param.Invert = new VideoParameter(devices[i].Invert ? 1 : 0);
                param.VFlip = new VideoParameter(devices[i].VFlip ? 1 : 0);
                param.HFOV = new VideoParameter(devices[i].HFOV);
            }

            Devices = deviceConfigs;
            Channels = channels;
        }

        // Config
        public List<VideoDeviceConfig> Devices { get; set; }
        public VideoChannelConfig[] Channels { get; set; }
        public long HiresBufferMaxSize { get; set; }
        public int MaxProcessThreadCount { get; set; }
    }
}
